package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.model.activity.OrderRecode;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {

    //  缓存列表 ，详情只需要从缓存中获取就可以了。
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;

    @Autowired
    private RabbitService rabbitService;

    @Override
    public List<SeckillGoods> findAll() {
        //  hvals key;
        String seckillKey = RedisConst.SECKILL_GOODS;
        List<SeckillGoods>  seckillGoodsList = this.redisTemplate.opsForHash().values(seckillKey);
        return seckillGoodsList;
    }

    @Override
    public Result checkOrder(Long skuId, String userId) {
        /*
        1.	判断用户在缓存中是否存在？
                seckill:user:2

        2.	判断用户抢单是否成功?
                seckill:orders  skuId value;

                提示抢购成功 去下单!

        3.	判断用户是否下过订单?
                判断缓存中 是否存在  SECKILL_ORDERS_USERS 这个 数据.

                如果下过订单，则提示 抢购成功 我的订单！

        4.	判断一下状态位！
                是否为空，或 0
         */
        String seckillUserKey = RedisConst.SECKILL_USER+userId;
        Boolean result = this.redisTemplate.hasKey(seckillUserKey);
        //  判断
        if (result){
            //  说明用户到了抢购这一步.
            //  判断用户是否有预下单记录.
            //  this.redisTemplate.opsForHash().put(RedisConst.SECKILL_ORDERS,userId,orderRecode);
            Boolean exist = this.redisTemplate.opsForHash().hasKey(RedisConst.SECKILL_ORDERS, userId);
            //  判断
            if (exist){
                // exist=true; 说明抢购成功 去下单!
                //  hget key field;
                OrderRecode orderRecode = (OrderRecode) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_ORDERS, userId);
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }
        }

        //  判断用户是否真正下过订单。 如果下过订单，则提示抢购成功，去看我的订单.
        //  用户真正下过订单之后，存储的数据 key = seckill:orders:users field = userId value = orderId;
        //  判断
        String orderKey = RedisConst.SECKILL_ORDERS_USERS;
        Boolean flag = this.redisTemplate.opsForHash().hasKey(orderKey, userId);
        if (flag){
            //  flag = true; 说明用户已经提交过订单。则提示抢购成功，去看我的订单.
            String orderId = (String) this.redisTemplate.opsForHash().get(orderKey, userId);
            return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);
        }

        //  检查状态位.
        String  status = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isEmpty(status) || "0".equals(status)){
            return  Result.build(null, ResultCodeEnum.SECKILL_ILLEGAL);
        }

        //  默认值 排队中...
        return Result.build(null,ResultCodeEnum.SECKILL_RUN);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void seckillStock(Long skuId) {
        /*
        1.  剩余库存数
        2.  获取秒杀商品对象
        3.  更新数据
         */
        Long count = this.redisTemplate.opsForList().size(RedisConst.SECKILL_STOCK_PREFIX + skuId);

        //  获取缓存中的秒杀商品对象。
        // SeckillGoods seckillGoodsRedis = (SeckillGoods) this.redisTemplate.opsForHash().get(seckillKey, skuId.toString());
        SeckillGoods seckillGoods = this.getSeckillGoodsById(skuId);

        //  赋值操作.
        seckillGoods.setStockCount(count.intValue());

        //  更新数据库.
        this.seckillGoodsMapper.updateById(seckillGoods);

        //  更新缓存
        this.redisTemplate.opsForHash().put(RedisConst.SECKILL_GOODS,skuId.toString(),seckillGoods);

    }

    //  预下单业务
    @Override
    public void seckillOrder(String userId, Long skuId) {
        /*
        1.  校验状态位。
        2.  判断用户是否下过订单。
        3.  从缓存中的剩余库存中，减库存。
        4.  将数据保存到缓存中。
        5.  预下单成功之后，减库存。
         */
        //  获取状态位
        String status = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isEmpty(status) || "0".equals(status)){
            return;
        }

        //   保证每个人只能下过一次订单， setnx 命令。
        //  定义一个用户的缓存key    seckill:user:2
        String seckillUserKey = RedisConst.SECKILL_USER+userId;
        //  判断  exist = true; 执行成功，表示当前用户第一次 抢购。 如果是false 表示缓存中已经存在，用户不是第一次抢购了。
        Boolean exist = this.redisTemplate.opsForValue().setIfAbsent(seckillUserKey, userId, RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);
        if (!exist){
            return;
        }

        //  预减库存操作.  seckill:stock:23
        String stockSkuId = (String) this.redisTemplate.opsForList().rightPop(RedisConst.SECKILL_STOCK_PREFIX+skuId);
        //  判断
        if (StringUtils.isEmpty(stockSkuId)){
            //  如果stockSkuId是空，则说明商品没有库存了，已售罄。 赶紧通知其他兄弟节点。
            this.redisTemplate.convertAndSend("seckillpush",skuId+":0");
            return;
        }
        //  保存预下单数据到缓存.  缓存的数据类型。 hset key=seckill:orders field=userId value;
        OrderRecode orderRecode = new OrderRecode();
        orderRecode.setNum(1);
        orderRecode.setUserId(userId);
        orderRecode.setSeckillGoods(this.getSeckillGoodsById(skuId));
        orderRecode.setOrderStr(MD5.encrypt(userId+skuId));

        this.redisTemplate.opsForHash().put(RedisConst.SECKILL_ORDERS,userId,orderRecode);

        //  更新剩余库存数. redis， mysql. 编写一个方法. 也可以使用异步发送消息的形式解决.
        //  方式一：同步操作
        //  redis 存储。 key = seckill:goods field = skuId value = SeckillGood;
        //  hset key field value;
        //  this.updateSeckillStock(skuId);

        //  方式二：发送消息
        this.rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_SECKILL_STOCK,MqConst.ROUTING_SECKILL_STOCK,skuId);

    }

    //  更新秒杀剩余库存. 如果这样写的话，可能会有多线程抢占资源，为了保证数据一致性，就需要加锁{本地锁，分布式锁}
    //  采用加分布式锁. 如果数据库更新失败了，则不能让缓存执行. 添加一个事务.
    //  加分布式锁：那么就会带来，性能降低. 秒杀讲究的是速度要快一点. 可以采用异步发送消息的方式解决，提高秒杀的响应速度.
    @Transactional(rollbackFor = Exception.class)
    public void updateSeckillStock(Long skuId) {
        //  更新数据库，更新缓存.
        //  创建对象 设置更新的数据.
        //  SeckillGoods seckillGoods = new SeckillGoods();
        //  设置剩余库存数. 剩余库存数，就是list 队列的长度.
        Long count = this.redisTemplate.opsForList().size(RedisConst.SECKILL_STOCK_PREFIX + skuId);
        //  seckillGoods.setStockCount(count.intValue());
        //  设置更新的条件. skuId
        //  QueryWrapper<SeckillGoods> seckillGoodsQueryWrapper = new QueryWrapper<>();
        //  seckillGoodsQueryWrapper.eq("sku_id",skuId);
        //  this.seckillGoodsMapper.update(seckillGoods,seckillGoodsQueryWrapper);

        //  能不能使用updateById 更新.
        //  更新缓存..... hset key field value     value = SeckillGoods;
        //  不能直接使用 【seckillGoods】 因为 这个对象中 只有一个剩余库存数属性是有数据的，其他都为空.
        //  直接使用hget 命令，获取到缓存中的原有数据.
        String seckillKey = RedisConst.SECKILL_GOODS;
        SeckillGoods seckillGoodsRedis = (SeckillGoods) this.redisTemplate.opsForHash().get(seckillKey, skuId.toString());
        seckillGoodsRedis.setStockCount(count.intValue());

        //  更新数据库. 按照Id 进行更新数据库.
        this.seckillGoodsMapper.updateById(seckillGoodsRedis);
        //  更新缓存数据.
        this.redisTemplate.opsForHash().put(seckillKey,skuId.toString(),seckillGoodsRedis);

    }


    @Override
    public SeckillGoods getSeckillGoodsById(Long skuId) {
        //  hget key field;
        SeckillGoods seckillGoods = (SeckillGoods) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_GOODS, skuId.toString());
        return seckillGoods;
    }
}
